import { NavigatePlane } from './navigate-plane';
import { DefaultStage } from '../default-stage';
import { AnimationFrame, ArcRotateCamera, BabylonContainer, Utils } from 'babylon-lib/index';
import { VirtualJoystick } from './virtual-joystick';
import { Quaternion, Vector2, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { DeviceInputPreset } from 'babylon-lib/preset/device-input/device-input-preset';
import { PointerInfo } from '@babylonjs/core/Events/pointerEvents';
import { DeviceInputKey, InputStatus } from 'babylon-lib/preset/device-input/device-information';
import { RawAssetsUrl } from '../../raw-asset-info';
import { BoundingBox } from '@babylonjs/core/Culling/boundingBox';
import { CLICK_THRESHOLD } from '../../data/data';


class NavigateInput {

    private static _instance: NavigateInput;
    private _stage: DefaultStage;
    private _direction = new Vector3();
    private _moveSpeed = 0.2;
    private _moveReq = {};
    private _moveDamping = 0.6;
    private _moveZone: BoundingBox;
    private _tv0 = new Vector3();

    public static get instance(): NavigateInput {
        return NavigateInput._instance;
    }

    constructor(stage: DefaultStage) {
        this._stage = stage;
        (stage.mainCamera as ArcRotateCamera).panningSensibility=1e6;
        NavigatePlane.initialize(stage);
        this._moveZone = NavigatePlane.plane.getBoundingInfo().boundingBox;
        if (Utils.isPC()) {
            this.setKeyInput();
        } else {
            this.setTouchInput();
            this.setVirtualJoystickInput();
        }

        NavigateInput._instance = this;
    }

    private setVirtualJoystickInput(): void {
        VirtualJoystick.initialize(this._stage);
        const loader = this._stage.loader(this._stage.mainScene);
        VirtualJoystick.fromTexture = loader.getTextureTaskInfo(RawAssetsUrl.textureTouchOuter)?.texture!;
        VirtualJoystick.toTexture = loader.getTextureTaskInfo(RawAssetsUrl.textureTouchCenter)?.texture!;
    }

    private setTouchInput(): void {
        const beginPointer = new Vector2();
        const endPointer = new Vector2();
        const camera = this._stage.mainCamera as ArcRotateCamera;
        const stopSensibility = 1e6;
        const moveSensibility = 1e3;
        let isDown = false;
        let isLeft = false;
        let isClick: boolean = true;
        DeviceInputPreset.onInput((e?: PointerInfo) => {
            if (!e) return;
            if (isDown) return;
            isClick = true;
            isDown = true;
            beginPointer.set(e.event.clientX, e.event.clientY);
            isLeft = beginPointer.x < window.innerWidth * 0.5;
            if (isLeft) {
                camera.angularSensibilityX = camera.angularSensibilityY = stopSensibility;
                VirtualJoystick.update(e.event.clientX, e.event.clientY);
            } else {
                camera.angularSensibilityX = camera.angularSensibilityY = moveSensibility;
            }
        }, InputStatus.down, DeviceInputKey.pointer);

        DeviceInputPreset.onInput((e?: PointerInfo) => {
            if (!e) return;
            if (!isDown) return;
            endPointer.set(e.event.clientX, e.event.clientY);
            if (isClick) isClick = Vector2.DistanceSquared(beginPointer, endPointer) < CLICK_THRESHOLD;
            if (isLeft) {
                if (!isClick) {
                    this._direction.set(endPointer.x - beginPointer.x, 0, endPointer.y - beginPointer.y);
                    VirtualJoystick.update(endPointer.x, endPointer.y);
                    VirtualJoystick.active = true;
                    this.checkNavigatePlane();
                    this.moveUpdate();
                }
            }
        }, InputStatus.move, DeviceInputKey.pointer);

        DeviceInputPreset.onInput(() => {
            if (!isDown) return;
            isDown = false;
            if (isLeft) {
                isLeft = false;
                camera.angularSensibilityX = camera.angularSensibilityY = moveSensibility;
                this._direction.set(0, 0, 0);
                VirtualJoystick.update();
                VirtualJoystick.active = false;
                if(!isClick){
                    this.checkNavigatePlane();
                    this.moveUpdate();
                }

            }

        }, InputStatus.up, DeviceInputKey.pointer);
    }

    private setKeyInput(): void {
        const forwardKey = [DeviceInputKey.keyboardW];
        DeviceInputPreset.onInput((e) => {
            this._direction.z = -1;
            this.checkNavigatePlane();
            this.moveUpdate();
        }, InputStatus.down, ...forwardKey);
        DeviceInputPreset.onInput((e) => {
            this._direction.z = 0;
            this.checkNavigatePlane();
            this.moveUpdate();
        }, InputStatus.up, ...forwardKey)

        const backwardKey = [DeviceInputKey.keyboardS];
        DeviceInputPreset.onInput((e) => {
            this._direction.z = 1;
            this.checkNavigatePlane();
            this.moveUpdate();
        }, InputStatus.down, ...backwardKey);
        DeviceInputPreset.onInput((e) => {
            this._direction.z = 0;
            this.checkNavigatePlane();
            this.moveUpdate();
        }, InputStatus.up, ...backwardKey)

        const leftKey = [DeviceInputKey.keyboardA];
        DeviceInputPreset.onInput((e) => {
            this._direction.x = -1;
            this.checkNavigatePlane();
            this.moveUpdate();
        }, InputStatus.down, ...leftKey);
        DeviceInputPreset.onInput((e) => {
            this._direction.x = 0;
            this.checkNavigatePlane();
            this.moveUpdate();
        }, InputStatus.up, ...leftKey)

        const rightKey = [DeviceInputKey.keyboardD];
        DeviceInputPreset.onInput((e) => {
            this._direction.x = 1;
            this.checkNavigatePlane();
            this.moveUpdate();
        }, InputStatus.down, ...rightKey);
        DeviceInputPreset.onInput((e) => {
            this._direction.x = 0;
            this.checkNavigatePlane();
            this.moveUpdate();
        }, InputStatus.up, ...rightKey)

    }

    private checkNavigatePlane(): void {
        NavigatePlane.active = this._direction.x === 0 && this._direction.z === 0;
    }

    private moveUpdate(): void {
        if (AnimationFrame.isRunning(this._moveReq)) return;
        const camera = this._stage.mainCamera as ArcRotateCamera;
        const dir = this._direction;
        const camDir = new Vector3();
        const wDir = new Vector3();
        const mDir = new Vector3();
        const rot = Quaternion.Identity();
        let spd = 0;
        AnimationFrame.request(() => {
            camera.getDirectionToRef(BabylonContainer.forward, camDir);
            camDir.y = 0;
            camDir.normalize();
            Quaternion.FromUnitVectorsToRef(BabylonContainer.forward, camDir, rot);
            if (dir.x === 0 && dir.z === 0) {
                spd *= this._moveDamping;
                mDir.applyRotationQuaternionToRef(rot, wDir);
                wDir.normalize().scaleInPlace(spd);
                if (this.checkZone(camera.target, wDir)) {
                    camera.target.addInPlace(wDir);
                }
                if (spd <= 1e-6) return true;
            } else {
                spd = this._moveSpeed;
                mDir.copyFrom(dir);
                mDir.applyRotationQuaternionToRef(rot, wDir);
                wDir.normalize().scaleInPlace(spd);
                if (this.checkZone(camera.target, wDir)) {
                    camera.target.addInPlace(wDir);
                }
            }
        }, 16, 200, this._moveReq);
    }

    private checkZone(target: Vector3, delta: Vector3): boolean {
        const zon = this._moveZone;
        const tar = this._tv0.copyFrom(target);
        tar.addInPlace(delta);
        if (tar.x < zon.minimumWorld.x) return false;
        if (tar.x > zon.maximumWorld.x) return false;
        if (tar.z < zon.minimumWorld.z) return false;
        if (tar.z > zon.maximumWorld.z) return false;
        return true;
    }

}


export { NavigateInput };